Skip to content

feat(observability): opt-in LangFuse callback for litellm calls#198

Open
taxfree-python wants to merge 1 commit intohuggingface:mainfrom
taxfree-python:feat/langfuse-callback
Open

feat(observability): opt-in LangFuse callback for litellm calls#198
taxfree-python wants to merge 1 commit intohuggingface:mainfrom
taxfree-python:feat/langfuse-callback

Conversation

@taxfree-python
Copy link
Copy Markdown

Summary

Refs #196.

Adds a small, env-gated hook that registers litellm's native LangFuse callback when LANGFUSE_HOST + LANGFUSE_PUBLIC_KEY + LANGFUSE_SECRET_KEY are all set. With any of the three unset the integration is a no-op and runtime behavior is identical to today.

The primary HF-Dataset-based telemetry pipeline (agent/core/telemetry.py) is unchanged — this is purely an additional side channel for operators who want LLM traces in their own LangFuse instance.

Why mandatory LANGFUSE_HOST

litellm's default LangFuse host is cloud.langfuse.com. If the gate were just PUBLIC_KEY + SECRET_KEY, an operator who only meant to set keys for a self-hosted instance — but forgot LANGFUSE_HOST — would silently exfiltrate prompts to a third-party SaaS. Requiring LANGFUSE_HOST makes "where does the data go" a conscious decision. Both deployment shapes are supported:

  • Self-host: LANGFUSE_HOST=https://langfuse.internal.example.com
  • SaaS: LANGFUSE_HOST=https://cloud.langfuse.com

Scope (intentionally minimal)

  • New agent/core/observability.py with setup_langfuse() (env-gated, idempotent)
  • One-line call from agent/config.py:load_config() — covers both CLI (agent/main.py) and backend (backend/session_manager.py module-init) without a separate hook
  • pyproject.toml: new [observability] optional-dep group; langfuse is not a hard dep
  • agent/README.md: new "Observability (optional)" section
  • tests/unit/test_observability.py: gate-passes / gate-blocks / idempotent

Out of scope (deferred to a follow-up if there's interest): forwarding the existing kind tag from PR #179, plus session.user_id / session.session_id, as metadata={...} to each litellm call. That's a mechanical change touching the 7 acompletion call sites and is much easier to review on its own.

Test plan

  • pytest tests/unit/test_observability.py -v — 5 cases pass (1 gate-passes, 3 parametrized gate-blocks, 1 idempotent)
  • pytest tests/unit/test_config.py — existing config tests still pass (no load_config semantics change when env vars unset)
  • Maintainer manual: with LANGFUSE_* unset, run the agent — no log lines about LangFuse, no errors
  • Maintainer manual: with vars set + a LangFuse instance reachable, run the agent — "LangFuse observability enabled" log line + traces visible in the LangFuse UI

Note on CI

The claude-code-action review workflow fails on every external-fork PR (pull_request triggers don't grant write tokens to fork actors — confirmed across recent fork PRs). The red review check is expected and not contributor-fixable; the change itself does not alter any workflow.

Adds an env-gated hook that registers litellm's LangFuse OTEL callback
when the host and both keys are set. With any var unset the integration
is a no-op and behavior is unchanged.

The host is mandatory by design so the destination is always an explicit
choice (self-hosted or SaaS) — no silent fallback to litellm's default
endpoint. Either env-var name is accepted: Langfuse SDK v4's docs issue
credentials as LANGFUSE_BASE_URL while litellm reads LANGFUSE_HOST, so we
mirror BASE_URL into HOST when only the former is set.

Uses litellm's `langfuse_otel` callback rather than the legacy `langfuse`
one, which breaks against Langfuse SDK v4 with
`module 'langfuse' has no attribute 'version'` — the OTEL path works
against both v3 and v4.

The HF-Dataset-based primary telemetry pipeline is untouched; this is an
additional side channel.

Refs huggingface#196.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@taxfree-python taxfree-python force-pushed the feat/langfuse-callback branch from 226f3d5 to 63598bb Compare May 1, 2026 10:04
@taxfree-python
Copy link
Copy Markdown
Author

Force-pushed an update with two changes after end-to-end testing against a self-hosted Langfuse instance:

  1. Switched callback from langfuse to langfuse_otel. The legacy langfuse integration in current litellm releases breaks against Langfuse SDK v4 (AttributeError: module 'langfuse' has no attribute 'version'). The OTEL path works against both v3 and v4 and is what Langfuse v4 docs recommend.

  2. Accept LANGFUSE_BASE_URL as an alias for LANGFUSE_HOST. Langfuse SDK v4's setup docs issue credentials as LANGFUSE_BASE_URL, but litellm's callback only reads LANGFUSE_HOST — without this alias the gate would silently no-op for anyone copy-pasting from Langfuse's own docs. We accept either and mirror BASE_URL into HOST so litellm sees it. HOST takes precedence if both are set.

Verified end-to-end with litellm.completion(...) + langfuse_otel: trace lands in the configured Langfuse instance, retrievable via /api/public/traces. Tests cover both env-var names plus precedence; all 7 observability tests pass.

@fglogan
Copy link
Copy Markdown

fglogan commented May 3, 2026

closed per maintainer request

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants